package net.lastcoder.ifserver.web;

import net.lastcoder.ifserver.nio.Connection;
import net.lastcoder.ifserver.nio.ConnectionCloseListener;
import net.lastcoder.ifserver.nio.NIOConnection;
import net.lastcoder.ifserver.packet.Packet;
import net.lastcoder.ifserver.session.Session;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

/**
 * auth: WenYF
 * date: 2017/3/13
 */
public class WebConnection implements Connection {
    private static final Logger Log = LoggerFactory.getLogger(NIOConnection.class);

    private Session session;
    private org.eclipse.jetty.websocket.api.Session ioSession;

    private ConnectionCloseListener closeListener;

    private AtomicReference<Connection.State> state = new AtomicReference<>(Connection.State.OPEN);
    private final ReentrantLock ioSessionLock = new ReentrantLock(true);

    public WebConnection(org.eclipse.jetty.websocket.api.Session session) {
        this.ioSession = session;
    }

    @Override
    public Session getOwner() {
        return session;
    }

    @Override
    public boolean validate() {
        if (isClosed()) {
            return false;
        }
        deliverRawData(" ".getBytes());
        return !isClosed();
    }

    @Override
    public void registerCloseListener(ConnectionCloseListener listener, Object ignore) {
        if (closeListener != null) {
            throw new IllegalStateException("Close listener already configured");
        }
        if (isClosed()) {
            listener.onConnectionClose(session);
        }
        else {
            closeListener = listener;
        }
    }

    @Override
    public void removeCloseListener(ConnectionCloseListener listener) {
        if (closeListener == listener) {
            closeListener = null;
        }
    }

    @Override
    public byte[] getAddress() throws UnknownHostException {
        final SocketAddress remoteAddress = ioSession.getRemoteAddress();
        if (remoteAddress == null) throw new UnknownHostException();
        final InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress;
        final InetAddress address = socketAddress.getAddress();
        return address.getAddress();
    }

    @Override
    public String getHostAddress() throws UnknownHostException {
        final SocketAddress remoteAddress = ioSession.getRemoteAddress();
        if (remoteAddress == null) throw new UnknownHostException();
        final InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress;
        final InetAddress inetAddress = socketAddress.getAddress();
        return inetAddress.getHostAddress();
    }

    @Override
    public String getHostName() throws UnknownHostException {
        final SocketAddress remoteAddress = ioSession.getRemoteAddress();
        if (remoteAddress == null) throw new UnknownHostException();
        final InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress;
        final InetAddress inetAddress = socketAddress.getAddress();
        return inetAddress.getHostName();
    }

    @Override
    public void close() {
        if (state.compareAndSet(Connection.State.OPEN, Connection.State.CLOSED)) {

            if (session != null) {
                session.setStatus( Session.STATUS_CLOSED );
            }

            try {
                // TODO 关闭连接消息
                IoBuffer buffer = IoBuffer.allocate(1);
                buffer.setAutoExpand(false);
                byte b = '\0';
                buffer.put(b);

                buffer.flip();
                ioSessionLock.lock();
                try {
                    if (ioSession.isOpen()) {
                        ioSession.getRemote().sendBytes(buffer.buf());
                    }
                }
                finally {
                    ioSessionLock.unlock();
                }
            } catch (Exception e) {
                Log.error("Failed to deliver stream close tag: " + e.getMessage(), e);
            }

            try {
                ioSession.close();
            } catch (Exception e) {
                Log.error("Exception while closing WebSocket session", e);
            }

            notifyCloseListeners(); // clean up session, etc.
        }
    }

    @Override
    public void systemShutdown() {
        // 关闭连接消息
        close();
    }

    private void notifyCloseListeners() {
        if (closeListener != null) {
            try {
                closeListener.onConnectionClose(session);
            } catch (Exception e) {
                Log.error("Error notifying listener: " + closeListener, e);
            }
        }
    }

    @Override
    public void init(Session owner) {
        session = owner;
    }

    @Override
    public boolean isClosed() {
        return state.get() == Connection.State.CLOSED;
    }


    @Override
    public void deliver(Packet packet) {
        if (packet == null) {
            return;
        }
        if (!isClosed()) {
            boolean errorDelivering = false;
            try {
                ioSessionLock.lock();
                try {
                    ioSession.getRemote().sendString(packet.getJsonString());
                } finally {
                    ioSessionLock.unlock();
                }
            }
            catch (Exception e) {
                Log.error("Error delivering Packet:\n" + packet, e);
                errorDelivering = true;
            }
            if (errorDelivering) {
                close();
            }
        }
    }

    @Override
    public void deliverRawData(byte[] data) {
        if (!isClosed()) {
            boolean errorDelivering = false;
            IoBuffer buffer = IoBuffer.allocate(data.length);
            buffer.setAutoExpand(true);
            try {
                buffer.put(data);

                buffer.flip();
                ioSessionLock.lock();
                try {
                    ioSession.getRemote().sendBytes(buffer.buf());
                    ioSession.getRemote().flush();
                }
                finally {
                    ioSessionLock.unlock();
                }
            }
            catch (Exception e) {
                Log.error("Error delivering raw data:\n" + Arrays.toString(data), e);
                errorDelivering = true;
            }

            // Attempt to close the connection if delivering text fails.
            if (errorDelivering) {
                close();
            }
        }
    }

    @Override
    public void deliverBuffer(IoBuffer buffer) {
        if (!isClosed()) {
            boolean errorDelivering = false;
            try {
                buffer.flip();
                ioSessionLock.lock();
                try {
                    ioSession.getRemote().sendBytes(buffer.buf());
                    ioSession.getRemote().flush();
                }
                finally {
                    ioSessionLock.unlock();
                }
            }
            catch (Exception e) {
                Log.error("Error delivering raw data:\n" + Arrays.toString(buffer.array()), e);
                errorDelivering = true;
            }

            // Attempt to close the connection if delivering text fails.
            if (errorDelivering) {
                close();
            }
        }
    }

    @Override
    public void deliverRawData(byte[] data, int off, int len) {
        if (!isClosed()) {
            boolean errorDelivering = false;
            IoBuffer buffer = IoBuffer.allocate(data.length);
            buffer.setAutoExpand(true);
            try {
                buffer.put(data, off, len);

                buffer.flip();
                ioSessionLock.lock();
                try {
                    ioSession.getRemote().sendBytes(buffer.buf());
                }
                finally {
                    ioSessionLock.unlock();
                }
            }
            catch (Exception e) {
                Log.error("Error delivering raw data:\n" + Arrays.toString(data), e);
                errorDelivering = true;
            }

            // Attempt to close the connection if delivering text fails.
            if (errorDelivering) {
                close();
            }
        }
    }

    private static class ThreadLocalEncoder extends ThreadLocal<CharsetEncoder> {
        @Override
        protected CharsetEncoder initialValue() {
            return StandardCharsets.UTF_8.newEncoder()
                    .onMalformedInput(CodingErrorAction.REPORT)
                    .onUnmappableCharacter(CodingErrorAction.REPORT);
        }
    }

    @Override
    public String toString() {
        return "NIOConnection{" +
                "ioSession=" + ioSession +
                ", state=" + state +
                '}';
    }
}
